""" Some miscellaneous functions and classes that I couldn't find a place for.

    Special thanks to R. Barren for relpath.
"""

import os
import operator
from math import *
from UserDict import DictMixin

__all__ = (
    'strbool',
    'sum_multi',
    'OrderedDict',
    'ProcDict',
    'AutoPropertyM'
)

# relpath.py
# R.Barran 30/08/2004
def relpath(target, base=os.curdir):
    """
    Return a relative path to the target from either the current dir or
    an optional base dir.
    Base can be a directory specified either as absolute or relative to
    current dir.
    """

    if not os.path.exists(target):
        raise OSError, 'Target does not exist: '+target

    if not os.path.isdir(base):
        raise OSError, 'Base is not a directory or does not exist: '+base

    base_list = (os.path.abspath(base)).split(os.sep)
    target_list = (os.path.abspath(target)).split(os.sep)

    # On the windows platform the target may be on a completely
    # different drive from the base.
    if os.name in ['nt','dos','os2'] and base_list[0] <> target_list[0]:
        raise OSError, 'Target is on a different drive to base. Target: '+\
        target_list[0].upper()+', base: '+base_list[0].upper()

    # Starting from the filepath root, work out how much of the filepath is
    # shared by base and target.
    for i in range(min(len(base_list), len(target_list))):
        if base_list[i] <> target_list[i]: break
    else:
        # If we broke out of the loop, i is pointing to the first differing
        # path elements.
        # If we didn't break out of the loop, i is pointing to identical path
        # elements.
        # Increment i so that in all cases it points to the first differing
        # path elements.
        i+=1

    rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
    return os.path.join(*rel_list)

# </end R.Barren>


def strbool(s):
    """ Returns whether or not a string evaluates to a boolean True or False.
        Possible string values for False are:
        '' (empty string), '0', 'no', 'false', 'off', 'none'
        Also, if any non-string object which evaluates to False is passed, False
        is returned.
        All other values will return True.
        The string is case insensitive and ignores whitespace padding.
    """
    if isinstance(s,str):
        s = s.lower().strip()
        return s not in ('','0','no','false','off','none')
    return bool(s)

def sum_multi(*seq):
    """ DEPRECATED.
        
        ** This function is depreceted. Just use map(sum, zip(*seq)) **
        ** directly. It's faster.                                    **
    
        Adds a sequence of 2-number sequences.
        The sequences must be of the same length.
        Returns a list of numbers.
        
        Example:
        
        >>> s1 = 2,3,5
        >>> s2 = 1,8,2.0
        >>> s3 = 8,7,6
        >>> sum_multi(s1,s2,s3)
        [5, 14, 12.0]
        
    """
    return map(sum, zip(*seq))

def copy_some(dest, src, keys=None):
    """ Copy some or all of the key-value pairs from dest into src.
    
        dest:   Destination mapping.
        src:    Source mapping.
        keys:   An optional list of keys of values to copy. If not
                provided (None), all keys are used.
    
        Use this function to copy some or all values from the sequence
        or mapping `src` into the corresponding slots of `dest`.
        
        Nothing is returned; dest is modified.
    """
    if keys is None:
        keys = src.keys()
    
    for k in keys:
        dest[k] = src[k]

class OrderedDict(dict):
    """ A dictionary that keeps its items in order.
        
        The best way to initialize an instance of this is not to pass a
        dictionary but to give a sequence of (key,value) pairs.
        
        >>> od = OrderedDict(('Name','Bob'),
        >>>                  ('Age',42),
        >>>                  ('Courier','A type of font'))
        >>> print od
        <OrderedDict ('Name':'Bob', 'Age':'42', 'Courier':'A type of font')>
        
        You MAY initialize with a dictionary but you can't predict the
        order in which the items will be added. You can also pass any
        object which has an iteritems method (such as another
        OrderedDict object) in which case the order may be determinable.
        
        >>> od = OrderedDict({'Color':'Blue', 'Food':'Fish', 'Carbon':'Diamond})
        >>> od2 = OrderedDict(od)
        >>> print od
        >>> print od2
        <OrderedDict (('Food','Fish'), ('Color','Blue'), ('Carbon','Diamond'))>
        <OrderedDict (('Food','Fish'), ('Color','Blue'), ('Carbon','Diamond'))>
        
        (Note the way OrderedDicts are represented. This is to give the best
        possible way to recreate an OrderedDict and maintain the order, although
        it obviously won't always be possible to recreate one exactly as
        represented.)
        
        You can also pass a list or other iterable, in which case each item will
        be interpreted as a (key,value) pair.
        
        >>> od = OrderedDict ([['Thing','Donkey'], ['Time','Late'], ['Pi',3.141]])
        >>> print od
        <OrderedDict (('Thing','Donkey'), ('Time','Late'), ('Pi',3.141))
        
        Finally, you can pass a series of key-value pairs.
        
        >>> od = OrderedDict(foo='Bar',spam='more spam')
        >>> print od
        <OrderedDict (('foo','Bar'), ('spam','more spam'))
        
        Note that you can use both keyword arguments and variable arguments
        toegther. The keyword arguments will always be last in order. Keyword
        arguments will overwrite values given in the variable arguments list if
        there are key conflicts.
        
        Special thanks to David Benjamin for the example provided at
        http://code.activestate.com/recipes/107747/ on which some of the code
        for this class is based.
    """
    
    def __init__(self,*args,**kwargs):
        self._keys = []
        dict.__init__(self)
        # If no arguments were passed,
        if len(args) == 0:
            pass # Do nothing
        # If only one arg was passed
        elif len(args) == 1:
            arg = args[0]
            # If it has key-value pairs
            if hasattr(arg,'iteritems'):
                for k,v in arg.iteritems():
                    self.set(k,v)
            # If it is an iterable,
            elif hasattr(arg,'__iter__'):
                for k,v in arg:
                    self.set(k,v)
            # If the argument is not iterable and does not have item iterator,
            else:
                raise TypeError("Object must be iterable or have iteritems "
                                "method for single-argument construction.")
        # If there was more than one argument passed,
        elif len(args) > 1:
            # Add arguments as key,value pairs.
            for k,v in args:
                self.set(k,v)
        # Add the keyword args.
        for k,v in kwargs.iteritems():
            self.set(k,v)
    
    def __repr__(self):
        # FIXME: Ugly code
        return "<%s (%s)>"%(self.__class__.__name__,
            ', '.join(["(%s,%s)"%(k.__repr__(),v.__repr__())
                        for k,v in self.iteritems()]))
    
    def __setitem__(self,key,value):
        if key not in self._keys:
            self._keys.append(key)
        dict.__setitem__(self,key,value)
    
    set = __setitem__
    
    def __delitem__(self,key):
        self._keys.remove(key)
        dict.__delitem__(self,key)
    
    def __iter__(self):
        return iter(self._keys)
    
    def keys(self):
        """ Returns list of item keys in order.
        
            NOTE: Adding, removing, or changing the values of items in the
            returned list will break the integrity of the OrderedDict! However,
            you may sort the list if needed, which will change the order of
            item in the OrderedDict.
        """
        return self._keys
    
    def values(self):
        """ Returns list of item values in order. """
        return map(self.get, self._keys)

    def items(self):
        " Returns a sequence of all items, in order, as (key,value) pairs. "
        return zip(self._keys, self.values())

    def iteritems(self):
        """ Returns an iterator that will go through items in order. """
        return iter(self.items())

    def copy(self):
        """ Creates a new OrderedDict object from self. """
        return OrderedDict(self)
    
    def setdefault(self, key, failobj = None):
        """ Sets a default value, adding it if it does not exist. """
        if key not in self._keys:
            self._keys.append(key)
        dict.setdefault(self, key, failobj)

    def update(self, dictObj):
        """ Like dict.update, but new objects are ordered last. """
        dict.update(self, dictObj)
        for key in dictObj.keys():
            if key not in self._keys:
                self._keys.append(key)

    def clear(self):
        """ Clears the OrderedDict, forgetting the order of items. """
        self._keys = []
        dict.clear(self)
        
    def change(self,oldkey,newkey):
        """ Replaces a key without changing the order or the item value.
            A ValueError occurs if the new key is already in use.
        """
        if newkey in self._keys:
            raise ValueError("New key %s is already in use."%newkey)
        self._keys.insert(self._keys.index(oldkey), newkey)
        self._keys.remove(oldkey)
        dict.__setitem__(self,newkey,self[oldkey])
        dict.__delitem__(self,oldkey)

class ProcDict(DictMixin):
    """ Wraps a dict and processes incoming values with functions.
    
        This can be treated mostly the same way as a dict, but
        keys and values of this dict come from another dict. In other
        words, it is a proxy to a dict, but not a dict itself.
    
        Attributes:
        
        dict        A dictionary to wrap. Keys and values of the
                    ProcDict object come from and go to here.
                    
        procs       A dictionary mapping keys to processing
                    functions. Any value assigned to a key of the
                    dict through the ProcDict object will be passed
                    through a converter function associated with the
                    key of procs that matches that of the assigned
                    value.
    """
    __slots__ = 'dict', 'procs'
    
    def __init__(self, dict, procs=None):
        self.dict = dict
        if procs is None:
            procs = {}
        self.procs = procs
    
    def __repr__(self):
        return "<ProcDict(%s)>"%self.dict
    
    def __getitem__(self,k):
        return self.dict[k]
    
    def __setitem__(self,k,v):
        if k in self.procs:
            v = self.procs[k](v)
        self.dict[k] = v
    
    def __delitem__(self,k):
        del self.dict[k]
    
    def keys(self):
        """ Returns all keys of the dict. """
        return self.dict.keys()


## This class is being put on hold until I'm sure I really need it.
#
#class NotifyProxy(object):
#    """ Notifies when values of another object are set.
#    
#        This class wraps another object and calls methods whenever
#        its values are set. It can also be told the names of
#        attributes of the object that are actually methods so that
#        when they are called the proxy can call other methods.
#    """
#    def __init__(self, obj, attrs, keys, methods=None):
#        """ Initialize a NotifyProxy object.
#        
#            obj:    The object to wrap.
#            attrs, keys:
#                    Dictionaries mapping attribute names and item keys
#                    to callables. When the the value of the attribute
#                    or key is set, the associated function will be
#                    called, and the returned values will be set to the
#                    object's corresponding attr/key.
#                    See below for details on the function format.
#            methods:
#                    Dictionary mapping names of methods in the object
#                    to callables. When these methods are called, the
#                    corresponding "co-method" will be called as well.
#            
#            
#            Format for attrs and keys:
#            
#            def set_attribute_or_key(proxy_obj, input_value)
#                ...
#                return output_value
#            
#            proxy_obj:      The NotifyProxy object.
#            input_value:    The value to be set to the corresponding
#                            attribute or item.
#            output_value:   The value to be 
#            
#        """
#        self.__object = obj
#        self.__attrs = {}
#        for k,v in attrs.iteritems():
#            self.__attrs[k] = v
#        self.__keys = {}
#        for k,v in keys.iteritems():
#            self.__keys[k] = v
#        self.__methods = {}
#        self._add_method__(self,methods)
#    
#    def __getitem__(self,key):
#        return self.__object[key]
#    
#    def __getattr__(self,attr):
#        return getattr(self.__object, attr)
#        
#    def __setitem__(self,key,val):
#        if key in self.__keys:
#            val = self.__keys[key](self,val)
#        self.__object[key] = val
#    
#    def __setattr__(self,attr,val):
#        if attr in self.__attrs:
#            val = self.__attrs[attr](self,val)
#        setattr(self.__object, attr, val)
#    
#    def _add_method__(self,meth
#